package net.hky365.lowcode.magic.interceptor;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.db.sql.SqlFormatter;
import net.hky365.lowcode.magic.utils.LoggerHelper;
import net.hky365.lowcode.magic.utils.MagicJdbcLogger;
import net.hky365.shop.framework.common.core.KeyValue;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.modules.db.BoundSql;
import org.ssssssss.magicapi.modules.db.SQLModule;
import org.ssssssss.magicapi.modules.db.inteceptor.SQLInterceptor;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 自定义打印SQL实现
 *
 * @author hl-king
 */
@Component
public class MagicSqlInterceptor implements SQLInterceptor {
    private final long timeout = Duration.ofHours(1).toMillis();
    TimedCache<Logger, MagicJdbcLogger> loggerCache = new TimedCache<>(timeout);

    private MagicJdbcLogger getLogger(RequestEntity requestEntity) {
        Logger logger = LoggerHelper.getLogger(requestEntity.getMagicScriptContext());
        return loggerCache.get(logger, true, () -> new MagicJdbcLogger(logger));
    }

    @Override
    public void preHandle(BoundSql boundSql, RequestEntity requestEntity) {
        MagicJdbcLogger logger = getLogger(requestEntity);
        if (!logger.isDebugEnabled() && !logger.isTraceEnabled()) {
            return;
        }

        if (logger.isTraceEnabled()) {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            Optional<KeyValue<String, StackTraceElement>> first = Arrays.stream(stackTrace).filter(it -> SQLModule.class.getName().equals(it.getClassName())).map(it -> new KeyValue<>(it.getMethodName(), it)).findFirst();
            logger.trace(true, "      Query: {}", first.map(KeyValue::getKey).orElse("Unknown"));
        }

        String parameters = Arrays.stream(boundSql.getParameters()).map(it -> {
            if (it == null) {
                return "null";
            }
            return MagicJdbcLogger.objectValueString(it) + "(" + it.getClass().getSimpleName() + ")";
        }).collect(Collectors.joining(", "));

        String dataSourceName = boundSql.getSqlModule().getDataSourceName();
        if (dataSourceName != null) {
            logger.debug(true, " dataSource：{}", dataSourceName);
        }
        logger.debug(true, "  Preparing: \n{}", SqlFormatter.format(boundSql.getSql()));
        logger.debug(true, " Parameters: {}", parameters);
    }

    @Override
    public Object postHandle(BoundSql boundSql, Object result, RequestEntity requestEntity) {
        MagicJdbcLogger logger = getLogger(requestEntity);
        if (!logger.isDebugEnabled() && !logger.isTraceEnabled()) {
            return result;
        }

        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        Optional<KeyValue<String, StackTraceElement>> first = Arrays.stream(stackTrace).filter(it -> SQLModule.class.getName().equals(it.getClassName())).map(it -> new KeyValue<>(it.getMethodName(), it)).findFirst();
        first.ifPresent(it -> {
            Object logObj = result;
            String title = "     Result: {}";
            // "insert"  "select" "update"
            if (it.getKey().endsWith("select") || it.getKey().endsWith("page")) {
                logObj = result instanceof List ? ((List) result).size() : result;
                title = "      Total: {}";
            } else if (it.getKey().endsWith("selectOne")) {
                logObj = result != null ? 1 : 0;
                title = "      Total: {}";
            } else if (it.getKey().endsWith("update")) {
                title = "    Updates: {}";
            }
            logger.debug(false, title, logObj);
        });
        logger.trace(false, "Result Data: {}", result);
        return result;
    }

    @Override
    public void handleException(BoundSql boundSql, Throwable throwable, RequestEntity requestEntity) {
        MagicJdbcLogger logger = getLogger(requestEntity);
        logger.trace(false, "      Error: ", throwable);
    }
}
